/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { hvigor, HvigorNode } from '@ohos/hvigor';
import { ErrorCode, PluginError } from './hmrouter_extension/error/PluginError';
import { HMRouterDefaultExtension } from './hmrouter_extension/HMRouterExtension';
import {
  ExtensionManager,
  isHspModule,
  Logger,
  PluginExecutionController,
  PluginFileUtil,
  PluginStore,
  registerPluginExtension,
  TsAstUtil,
} from './framework';
import { PluginParam } from './Index';

/**
 * HMRouter plugin manager class
 *
 * This singleton class is responsible for managing plugin tasks and coordinating
 * the execution flow of the plugin.
 * It handles the registration of plugins for different
 * module types and manages the lifecycle of plugin execution controllers.
 */
export class HMRouterPluginManager {
  // Singleton instance
  private static instance: HMRouterPluginManager | null = null;

  // Set of execution controllers for all registered plugin instances
  private controllerMap: Map<string, PluginExecutionController> = new Map();

  private moduleNodes: HvigorNode[] = [];

  private nodeIgnored: Set<string> = new Set();

  /**
   * Private constructor that registers Hvigor lifecycle event handlers
   *
   * This constructor:
   * 1. Register the default HMRouter extension
   * 2. Set up event handlers for a Hvigor build process
   */
  private constructor() {
    Logger.info('', 'HMRouterPlugin starting...');

    // Register the default HMRouter extension implementation
    registerPluginExtension(new HMRouterDefaultExtension());

    // Register handler for when Hvigor nodes are fully evaluated,
    // This is when we start the plugin execution
    hvigor.nodesEvaluated(() => {
      this.moduleNodes.forEach((node) => {
        if (this.controllerMap.has(node.getNodeName())) {
          return;
        }
        node.getAllPluginIds().forEach((pluginId) => {
          this.registerPlugin(node, pluginId, {});
        });
      });
      for (const [_key, controller] of this.controllerMap) {
        controller.start();
      }
    });

    // Register handler for when the Hvigor build process completes
    // This performs cleanup to prevent memory leaks
    hvigor.buildFinished(() => {
      // Execute completion phase for all controllers
      this.controllerMap.forEach((controller) => {
        controller.complete();
      });

      // Clean up resources to prevent memory leaks
      HMRouterPluginManager.instance = null;
      PluginStore.destroy();
      ExtensionManager.destroy();
      this.controllerMap.clear();
      TsAstUtil.clearProject();
    });
  }

  /**
   * Get the singleton instance of the plugin manager
   *
   * @returns {HMRouterPluginManager} The singleton instance
   */
  static getInstance(): HMRouterPluginManager {
    if (!HMRouterPluginManager.instance) {
      HMRouterPluginManager.instance = new HMRouterPluginManager();
    }
    return HMRouterPluginManager.instance;
  }

  /**
   * Register a plugin for a specific Hvigor node
   *
   * This method creates and configures an execution controller for the specified
   * Hvigor node and plugin type, then adds it to the controller set for later execution.
   *
   * @param {HvigorNode} node - The Hvigor node representing a module
   * @param {string} pluginId - The plugin ID corresponding to the module type (HAP, HSP, HAR)
   * @param {PluginParam} options - Plugin configuration options
   * @throws {PluginError} If the module context can't be obtained
   */
  registerPlugin(node: HvigorNode, pluginId: string, options: PluginParam): void {
    // Get the module context from the node
    const moduleContext = node.getContext(pluginId);

    // Verify the module context exists
    if (!moduleContext) {
      throw PluginError.create(ErrorCode.ERROR_CONFIG, node.getNodeName(), node.getNodePath());
    }

    // Create a new execution controller for this node
    const controller = new PluginExecutionController(
      node,
      moduleContext,
      this.nodeIgnored.has(node.getNodeName()),
      options.extensions
    );

    // Add it to the set of controllers
    this.controllerMap.set(node.getNodeName(), controller);

    // Special handling for HSP modules to track module names
    if (isHspModule(node)) {
      // Read the package information to get the package name
      let packageJsonPath = PluginFileUtil.pathResolve(node.getNodePath(), 'oh-package.json5');
      let packageJson = PluginFileUtil.readJson5(packageJsonPath);

      // Initialize HSP module info arrays if needed
      let hspNameArr = PluginStore.getInstance().get<string[]>('hspModuleNames');
      if (!hspNameArr) {
        PluginStore.getInstance().set('hspModuleNames', []);
      }

      let hspModulesInfo =
        PluginStore.getInstance().get<Array<{ moduleName: string; packageName: string }>>('hspModulesInfo');
      if (!hspModulesInfo) {
        PluginStore.getInstance().set('hspModulesInfo', []);
      }

      // Add to legacy array for backward compatibility (using package name)
      PluginStore.getInstance().get<string[]>('hspModuleNames')?.push(packageJson.name);

      // Add to new structured array with both names
      PluginStore.getInstance().get<Array<{ moduleName: string; packageName: string }>>('hspModulesInfo')?.push({
        moduleName: node.getNodeName(),
        packageName: packageJson.name,
      });
    }
  }

  public addModuleNode(node: HvigorNode, isIgnored: boolean): void {
    this.moduleNodes.push(node);
    if (isIgnored) {
      this.nodeIgnored.add(node.getNodeName());
    }
  }
}
